#!/usr/bin/env python
# encoding: utf-8
"""
@author:shucheng
@file:alipay.py
@time:2024/2/20 21:40
@desc 支付宝积分领取
"""
import enum
import os.path
import random
import time
from datetime import datetime

import uiautomator2 as u2

from daily.constant import config
from util import log_tool, string_tool


class AccountEnum(enum.Enum):
    """
    账户枚举类
    """
    MAIN_ACCOUNT = "112"
    SUB_ACCOUNT = "lsc"


class SwipeModeEnum(enum.Enum):
    """
    滑动模式
    """
    # 普通（垂直滑动距离随机）
    ORDINARY = "ordinary"
    # 上下（垂直滑动方向随机）
    UP_AND_DOWN = "up_and_down"


def change_account():
    """
    切换支付宝账户
    :return:
    """
    # 回到首页
    back_to_home()
    # 进入“我的”
    d(text="我的").click(timeout=5)
    # 点击“设置”
    d(description="设置").click()

    time.sleep(3)
    # 登录其他账号
    d.click(0.498, 0.822)

    time.sleep(3)
    # 第2个是当前没点的那个账号
    btn = d(resourceId="com.alipay.mobile.securitybiz:id/tv_am_item_account")
    btn[1].click(timeout=6)


def back_to_home():
    """
    回到首页
    :return:
    """
    # 如果出现“更多”按钮，说明已经在首页了（这个判断有点冗余，可以不用要）
    while not d(resourceId="com.alipay.android.phone.openplatform:id/more_app_text", text="更多").exists:

        # 如果“首页”卡签（可能有两种按钮出现）出现，则直接点击回到首页
        if d.xpath('//*[contains(@resource-id,"com.alipay.android.tablauncher:id")]').exists:
            shouye_btn1 = d(resourceId="com.alipay.android.tablauncher:id/tab_description", text="首页")
            if shouye_btn1.exists:
                shouye_btn1.click()
                break

            shouye_btn2 = d(resourceId="com.alipay.android.tablauncher:id/selected_fl")
            if shouye_btn2.exists:
                shouye_btn2.click()
                break

        # 如果没有出现“首页”tab标签，就要一直点回退
        d.press("back")
        time.sleep(1)


def back_to_member_top():
    """
    回到“会员”顶部
    :return:
    """
    while not d.xpath('//*[contains(@text,"已经连续签到")]').exists:
        d.swipe_ext("down", scale=0.8)


def search_box_input(input_text):
    """
    搜索框输入
    :param input_text:
    :return:
    """
    # 搜索“JJ象棋”
    d.set_fastinput_ime(True)  # 切换成FastInputIME输入法
    time.sleep(1)
    d.send_keys(input_text)  # adb广播输入
    time.sleep(1)
    d.set_fastinput_ime(False)  # 切换成正常的输入法


def swipe(duration=25, interval_up=6, finish_callback=None, swipe_mode=SwipeModeEnum.ORDINARY):
    """
    滑动方法
    :param duration: 整体时间间隔，默认为25s
    :param interval_up: 每次滑动动作的时间间隔上限，默认为6s
    :param finish_callback: 滑动结束后，执行的回调i函数
    :param swipe_mode: 滑动模式
    :return:
    """
    # 滑动15s结束（多3s更保险）
    start = time.time() * 1000
    while True:
        if time.time() * 1000 - start > duration * 1000:

            # 回调函数，如：结束后返回领取积分 return_get_point
            if finish_callback is not None: finish_callback()
            break

        # 设置每间隔随机的时间进行滑动，防止被风控
        time.sleep(random.randint(1, interval_up + 1))
        if swipe_mode == SwipeModeEnum.ORDINARY:
            # 垂直滑动距离随机
            d.swipe(51, 1095, 51, 878 + random.randint(-550, 100), random.uniform(0.5, 0.9))
        else:
            # 垂直滑动方向随机
            rand_num = random.randint(1, 2)
            if rand_num == 1:
                direction = "up"
            else:
                direction = "down"
            d.swipe_ext(direction, scale=0.8)


# 结束后返回领取积分
def return_get_point():
    btn = d.xpath('//*[@resource-id="android:id/content"]/android.widget.RelativeLayout[1]')

    # btn.click_exists(timeout=3)
    # 点击btn，如果3秒内能找到，则直接点击，否则直接返回
    if btn.wait(timeout=5) is not None:
        btn.click()
    else:
        # 结束后返回领取积分
        while not d(text="会员签到赚积分").exists(timeout=3):
            d.press('back')


# 回调-通用的简单操作（小鸡、蚂蚁、芭芭）
def simple_execute_common(goto_finish_btn):
    # goto_finish_btn = d.xpath('//*[contains(@text,"逛蚂蚁庄园喂小鸡")]/../*[@text="去完成"]')
    # goto_finish_btn = d.xpath('//*[contains(@text,"逛一逛蚂蚁森林")]/../*[@text="去完成"]')
    # goto_finish_btn = d.xpath('//*[contains(@text,"逛一逛芭芭农场")]/../*[@text="去完成"]')
    # goto_finish_btn = d.xpath('//*[contains(@text,"逛一逛领支付红包")]/../*[@text="去完成"]')
    # goto_finish_btn = d.xpath('//*[contains(@text,"逛一逛亚运火炬活动")]/../*[@text="去完成"]')

    # 点击btn，如果3秒内能找到，则直接点击，否则不做任何操作
    goto_finish_btn.click_exists(timeout=5)

    # 结束后返回领取积分
    return_get_point()


# 回调-通用的稍微复杂的操作（滑动浏览15秒红包会场、滑动浏览优品会场、逛优选好货）
def complex_execute_common(goto_finish_btn):
    while goto_finish_btn.wait(timeout=2) is not None:
        # 防止上面判断该按钮存在，之后该按钮就不存在的情况
        if not goto_finish_btn.exists:
            break

        # 进入浏览页面
        goto_finish_btn.click_exists(timeout=5)

        # 设置随机间隔
        time.sleep(random.randint(3, 5))

        # 这个目前是用来处理“滑动浏览优品会场”中的弹窗的
        if d(text="去逛逛").exists:
            d(text="去逛逛").click()

        # 滑动15s结束
        # 结束后返回领取积分
        swipe(finish_callback=return_get_point)

    time.sleep(random.randint(6, 15))


# 15秒出行优惠
def fifteen_out_discount_callback(goto_finish_btn):
    # 等待2秒后执行点击操作，如果找不到按钮，会报错
    goto_finish_btn.click(timeout=5)

    # 滑动15s结束
    # 无回调
    swipe()

    # 结束后返回领取积分
    return_get_point()


# 浏览借呗15秒
def browse_jiebei_fifteen(goto_finish_btn):
    # 等待2秒后执行点击操作，如果找不到按钮，会报错
    goto_finish_btn.click(timeout=2)

    swipe(interval_up=2, finish_callback=lambda: d.press('back'), swipe_mode=SwipeModeEnum.UP_AND_DOWN)


def fifteen_three_point(task_execute_success_dict, ignore_already_execute=True):
    # d.xpath('//*[contains(@text,"逛15秒赚3积分")]')
    task_name = "逛15秒赚3积分"

    if ignore_already_execute:
        if task_name in task_execute_success_dict and task_execute_success_dict[task_name]:
            logger.info(f"{task_name} 已执行完成，本次跳过")
            return

    btn = d.xpath(f'//*[contains(@text,"{task_name}")]')
    if not btn.wait(timeout=2) is not None:
        d.swipe_ext("up", scale=0.8)

    btn.click_exists(timeout=2)

    # 滑动15s结束
    # 无回调
    swipe()

    d.swipe_ext("down", scale=0.8)
    logger.info(f"【{task_name}】成功执行")
    task_execute_success_dict[task_name] = True


def get_trial_money(task_execute_success_dict):
    """
    领取余额宝体验金
    :param task_execute_success_dict:
    :return:
    """
    task_name = "余额宝体验金"
    if task_name in task_execute_success_dict and task_execute_success_dict[task_name]:
        logger.info(f"{task_name} 已执行完成，本次跳过")
        return

    # 通过搜索框，进入“余额宝体验金”功能
    click_home_page_search_box()

    # 搜索“余额宝体验金”
    search_box_input("余额宝体验金")
    time.sleep(8)

    d(text="搜索").click()

    # 领取余额宝体验金
    click_item_xpath = '//*[@resource-id="com.alipay.android.phone.businesscommon.globalsearch:id/feed_list"]//*/android.widget.FrameLayout[1]/*[@text="余额宝体验金"]'
    if (btn := d.xpath(click_item_xpath)).wait(timeout=20) is not None:
        btn.click()
        if d(text="戳这里").exists(timeout=10):
            d(text="戳这里").click()
            time.sleep(10)
            logger.info(f"【{task_name}】成功执行")
            task_execute_success_dict[task_name] = True

    # 回到首页
    back_to_home()


def get_gold_ticket(task_execute_success_dict):
    """
    领取黄金票
    :param task_execute_success_dict:
    :return:
    """
    task_name = "领取黄金票"
    if task_name in task_execute_success_dict and task_execute_success_dict[task_name]:
        logger.info(f"{task_name} 已执行完成，本次跳过")
        return

    # 通过搜索框，进入“领取黄金票”功能
    click_home_page_search_box()

    # 搜索“天天领黄金票”
    search_box_input("天天领黄金票")
    time.sleep(8)

    d(text="搜索").click()

    # 领取黄金票
    if d(text='每日最高领8份').exists(timeout=20):
        d(text='每日最高领8份').click()
        time.sleep(10)
        logger.info(f"【{task_name}】成功执行")
        task_execute_success_dict[task_name] = True

    # 回到首页
    back_to_home()


def click_home_page_search_box():
    """
    点击首页搜索框
    :return:
    """
    d.xpath('//*[contains(@resource-id,"com.alipay.android.phone.openplatform:id/home_title_search_word")]').click()


def enter_alipay_member_page():
    """
    进入支付宝会员页
    :return:
    """
    if not d(resourceId="com.alipay.mobile.nebulax.integration:id/textView_title", text="支付宝会员").exists(timeout=1):
        # 如果当前不在”支付宝会员“页，则从首页重新进入”支付宝会员“页
        # 回到首页
        back_to_home()
        # 进入“我的”
        d(text="我的").click(timeout=5)
        # 点击“支付宝会员”
        d.click(0.226, 0.229)
        time.sleep(3)

    # 如果有”全部领取“按钮，则进行点击（最多等3秒）
    d(text="全部领取").click_exists(timeout=3)


def _get_task_callback_dict(acct_name_enum: AccountEnum):
    """
    获取任务名和callback的对应关系
    :return:
    """

    def _base_get_task_callback_dict(_acct_name_enum, _task_config_arr, _task_callback_dict, _path_selector_dict):
        for task_config in _task_config_arr:
            callback = task_config["execute_function"]

            if callback is None:
                continue

            # 设置任务名和callback的对应关系
            task_name_arr = task_config["task_name_arr"]
            for task_name in task_name_arr:
                _task_callback_dict[task_name] = callback
                _path_selector_dict[task_name] = get_go_to_finish_btn_path_selector(_acct_name_enum, task_name)

    task_callback_dict = {}
    path_selector_dict = {}
    # 处理公共配置
    _base_get_task_callback_dict(acct_name_enum, task_config_arr, task_callback_dict, path_selector_dict)
    # 处理特殊配置
    _base_get_task_callback_dict(acct_name_enum, task_config_arr_by_account[acct_name_enum], task_callback_dict,
                                 path_selector_dict)
    return task_callback_dict, path_selector_dict


def get_go_to_finish_btn_path_selector(acct_name_enum: AccountEnum, task_name):
    """
    获取任务对应的“去完成”按钮
    :param acct_name_enum:
    :param task_name:
    :return:
    """
    btn_name = "去完成"
    # “逛15秒最新看车”所在层，下面再进入两层，筛选名称为“去完成”的控件（如果不确定有多少层，就用//*，/*代表一层）
    # logger.info(d.xpath('//*[contains(@text,"逛15秒最新看车")]/../*/*[@text="去完成"]').exists)
    # logger.info(d.xpath('//*[contains(@text,"逛15秒最新看车")]/../android.view.View[8]/android.widget.Button[1]').exists)
    # logger.info(d.xpath('//*[contains(@text,"逛15秒最新看车")]/..//*[@text="去完成"]').exists)
    # 参考链接：https://github.com/openatx/uiautomator2/blob/master/XPATH.md
    # https://blog.csdn.net/qq_50854790/article/details/123610184
    # 2023.12.23update 说明：btn的名称里是“ 去完成”，带了空格
    # 2024.1.7update 查找“去完成”按钮，path表达式有差异
    if task_name in ['逛0元下单', '逛精选好物15秒', '观看15秒视频领积分', '浏览借呗15秒']:
        n = 4
    else:
        if acct_name_enum == AccountEnum.MAIN_ACCOUNT:
            n = 3
        else:
            n = 3
    path_selector = f'//*[contains(@text,"{task_name}")]{"/.." * n}//*[contains(@text,"{btn_name}")]'
    return path_selector


# 任务执行配置（公用的配置）
task_config_arr = [
    {
        "task_name_arr": ["逛蚂蚁庄园喂小鸡", "逛一逛蚂蚁森林", "逛网商",
                          "逛一逛芭芭农场", "逛一逛蚂蚁新村", "逛一逛领支付红包",
                          "逛一逛淘金币频道", "逛一逛支付宝运动", "逛一逛机票比价",
                          "逛一逛点淘", "去逛一逛神奇海洋"],
        "execute_function": simple_execute_common
    },
    {
        "task_name_arr": ["15秒出行优惠", "逛15秒收集", "逛15秒短视频",
                          "逛15秒精彩内容", "逛15秒出行优惠频道", "逛15秒出行地铁频道",
                          "逛15秒最新看车资讯", "逛鲸探数字藏品", "观看15秒视频领积分",
                          "逛15秒出行加油服务", "逛0元下单", "逛精选好物15秒", "逛15秒健康频道",
                          "逛15秒医保报销政策"],
        "execute_function": fifteen_out_discount_callback
    },
    {
        "task_name_arr": ["滑动浏览15秒红包会场", "滑动浏览优品会场",
                          "优选好货", "逛热卖"],
        "execute_function": complex_execute_common
    }
]

# 任务执行配置（不同账户下特殊的配置）
task_config_arr_by_account = {
    AccountEnum.MAIN_ACCOUNT: [
        {
            "task_name_arr": ["去逛一逛神奇海洋"],
            "execute_function": simple_execute_common
        },
        {
            "task_name_arr": ["浏览借呗15秒"],
            "execute_function": browse_jiebei_fifteen
        },
    ],
    AccountEnum.SUB_ACCOUNT: []
}


def _batch_find_and_execute_task(task_callback_dict, task_execute_success_dict, path_selector_dict,
                                 ignore_already_execute=True, need_execute_task_name_array=None):
    """
    页面查找并执行任务
    :param task_callback_dict: 任务名和callback的对应关系
    :param task_execute_success_dict: 任务执行情况
    :param path_selector_dict: 每个任务“去完成”按钮的xpath表达式
    :param ignore_already_execute: 是否忽略掉已执行完成的任务，默认为True
    :param need_execute_task_name_array: 待执行的任务名数组（如果为None，表示现有的都要执行；其他情况下，数组中写几个就执行几个）
    :return:
    """
    if need_execute_task_name_array is not None and len(need_execute_task_name_array) == 0:
        logger.info("未指定要执行的任务，跳过 _batch_find_and_execute_task")
        return

    if need_execute_task_name_array is None:
        actual_execute_task_name_array = list(task_callback_dict.keys())

        if not ignore_already_execute:
            # 直接全部重置为False
            for k in task_execute_success_dict.keys():
                task_execute_success_dict[k] = False
        logger.info("need_execute_task_name_array为None，将执行该账户下所有任务")
    else:
        actual_execute_task_name_array = need_execute_task_name_array

        if not ignore_already_execute:
            # 将指定任务重置为False
            for k in task_execute_success_dict.keys():
                if k in need_execute_task_name_array:
                    task_execute_success_dict[k] = False
        logger.info(f"need_execute_task_name_array为 {need_execute_task_name_array}，将执行该账户下部分任务")

    retry_count = 0
    while True:
        retry_count = retry_count + 1

        # 向下滑一部分，切换到“逛15秒赚3积分”，这样能找到“换一换”按钮
        # 可以一次性滑完，也可以分多次滑
        # d.swipe_ext("up", scale=0.6)
        while not d(text="换一换").exists(timeout=0.5):
            # d.swipe(51, 1095, 51, 878)
            d.swipe_ext("up", scale=0.1)

        if retry_count > 1:
            d(text="换一换").click()

        for task_name in actual_execute_task_name_array:
            # 当指定了need_execute_task_name_array时，则只根据里面提供的任务名来执行
            if need_execute_task_name_array is not None and task_name not in need_execute_task_name_array:
                continue

            if task_name in task_execute_success_dict and task_execute_success_dict[task_name]:
                logger.info(f"{task_name} 已执行完成，本次跳过 retry_count {retry_count}")
                continue

            # 判断任务对应的“去完成”按钮是否存在
            path_selector = path_selector_dict[task_name]
            if d.xpath(path_selector).exists:
                # if d.xpath(path_selector).wait(timeout=2) is not None:
                logger.info(f'[debug] 第{retry_count}次已找到【{task_name}】，执行中...')
                goto_finish_btn = d.xpath(path_selector)
                callback = task_callback_dict[task_name]
                callback(goto_finish_btn)
                logger.info(f'[debug] 第{retry_count}次已找到【{task_name}】，并且任务执行完毕')
                task_execute_success_dict[task_name] = True
            else:
                logger.info(f'[debug] 第{retry_count}次未找到【{task_name}】')
                task_execute_success_dict[task_name] = False

        # 验证本轮是否执行完所有任务
        has_execute_all_task = True
        for task_name in actual_execute_task_name_array:
            # 如果有一个为False，说明还有没执行完的，则跳出本次检查，直接进入下一轮的执行
            has_execute_all_task = has_execute_all_task and task_name in task_execute_success_dict \
                and task_execute_success_dict[task_name]
            if not has_execute_all_task:
                break

        # 如果没有到达最高的重试次数，所有任务提前执行完了，则跳出当前本轮的任务执行
        if has_execute_all_task:
            logger.info("本轮所有任务已执行完毕")
            break

        # 最多重试10次
        if retry_count == 10:
            break

    # 打印执行结果
    for task_name in actual_execute_task_name_array:
        value = task_execute_success_dict[task_name]
        if value:
            logger.info(f"【{task_name}】成功执行")
        else:
            logger.info(f"【{task_name}】未找到“去完成”按钮，未执行操作")

    # 回到“会员”顶部（不然可能会出现下次定位不准的情况）
    back_to_member_top()


def execute_daily_task(acct_name_enum: AccountEnum, skip_get_gold_ticket=False, skip_get_trial_money=False,
                       skip_sign_in=False, skip_fifteen_three_point=False, ignore_already_execute=True,
                       need_execute_task_name_array=None):
    """
    执行日常任务
    :param acct_name_enum: 账户枚举
    :param skip_get_gold_ticket: 是否跳过“领取黄金票”
    :param skip_get_trial_money: 是否跳过“领取余额宝体验金”
    :param skip_sign_in: 是否跳过“每日签到”
    :param skip_fifteen_three_point: 是否跳过“15秒赚3积分”
    :param ignore_already_execute: 是否忽略掉已执行完成的任务，默认为True
    :param need_execute_task_name_array: 指定要批量执行的任务（[] 不执行；None 执行该账户下所有任务；任务名list 执行指定的任务）
    :return:
    """
    # 加载任务执行情况
    dict_file_path = _get_task_dict_file_path(acct_name_enum)
    task_execute_success_dict = string_tool.read_json(dict_file_path)
    logger.info(f"从 {dict_file_path} 加载的字典信息为：{task_execute_success_dict}")

    try:
        # ======================== 执行任务 start ========================
        # if not skip_get_gold_ticket:
        #     # 领取黄金票
        #     get_gold_ticket(task_execute_success_dict)
        #
        # if not skip_get_trial_money:
        #     # 领取余额宝体验金
        #     get_trial_money(task_execute_success_dict)

        if not skip_sign_in:
            # 进入支付宝会员页
            enter_alipay_member_page()
            # 进入“每日签到”
            d(text="每日签到").click()

        task_callback_dict, path_selector_dict = _get_task_callback_dict(acct_name_enum)

        # 批量执行任务
        _batch_find_and_execute_task(task_callback_dict, task_execute_success_dict, path_selector_dict,
                                     ignore_already_execute, need_execute_task_name_array)

        if not skip_fifteen_three_point:
            # 15秒赚3积分
            fifteen_three_point(task_execute_success_dict, ignore_already_execute)
        # ======================== 执行任务 end ========================
    finally:
        # 写入任务执行情况
        string_tool.write_json(dict_file_path, task_execute_success_dict, need_pretty=True)
        logger.info(f"写入 {dict_file_path} 的字典信息为：{task_execute_success_dict}")


def _get_task_dict_file_path(acct_name_enum: AccountEnum):
    """
    获取 task_execute_success_dict 对应文件存储路径
    :return:
    """
    today_str = datetime.now().strftime("%Y%m%d")
    dict_file_path = os.path.join(log_dir, f"task_execute_success_dict_{acct_name_enum.value}_{today_str}.json")
    return dict_file_path


def main_func():
    """
    主执行流程
    :return:
    """
    logger.info("================ _execute_daily_task START ================")
    execute_daily_task(AccountEnum.MAIN_ACCOUNT)
    logger.info("================ _execute_daily_task END ================\n")

    # 切换账号
    change_account()
    logger.info("================ 切换账号再执行一次任务 ================")

    logger.info("================ _execute_daily_task START ================")
    execute_daily_task(AccountEnum.SUB_ACCOUNT)
    logger.info("================ _execute_daily_task END ================")

    # if USE_CONFIG == Config.PC_CONFIG:
    #     from tkinter import messagebox as tm
    #     tm.showinfo("提示", "处理完成")

    from tkinter import messagebox as tm
    tm.showinfo("提示", "处理完成")


if __name__ == '__main__':
    log_dir = config.get('automate.alipay.log_dir')
    logger = log_tool.get_logger(log_file_dir=log_dir)
    # logger.info('alipay.py')

    # 通过usb连接
    connect_addr = config.get('automate.alipay.mobile.connect_addr')
    d = u2.connect(connect_addr)
    # 通过无线连接（要无线连接，需要先有线连接一次，不想再用了，最好要把监听给关了 参考链接：https://blog.csdn.net/weixin_40533164/article/details/128208731）
    # d = u2.connect('192.168.137.200:5555')
    # d = u2.connect('http://127.0.0.1:7555')
    d = u2.connect()

    # d.debug = True

    package_name = 'com.eg.android.AlipayGphone'
    # 清理“支付宝”进程
    d.app_stop(package_name)

    # 启动支付宝
    d.app_start(package_name)
    time.sleep(5)

    main_func()

    # 跳过其他所有任务，只执行“逛15秒赚3积分”
    # params_dict = {"skip_get_gold_ticket": True, "skip_get_trial_money": True, "skip_sign_in": True,
    #                "skip_fifteen_three_point": False, "ignore_already_execute": False,
    #                "need_execute_task_name_array": []}
    # execute_daily_task(AccountEnum.MAIN_ACCOUNT, **params_dict)
